#include "pch.hpp"
#include "XmlDomStream.hpp"




XmlDomWriterStream::XmlDomWriterStream(QXmlStreamWriter& xmlStreamWriter)
	: m_xmlStreamWriter(xmlStreamWriter)
{}
///////////////////////////////////////////////////////////////////////////////////////////////////

XmlDomWriterStream::~XmlDomWriterStream()
{
	while(! m_openElements.isEmpty())
	{
		m_xmlStreamWriter.writeEndElement();
		m_openElements.pop();
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void XmlDomWriterStream::writeAttribute(QString qualifiedName, QVariant value)
{
	m_attributes << qualifiedName;
	m_xmlStreamWriter.writeAttribute(qualifiedName, value.toString());
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void XmlDomWriterStream::writeCDataSection(QString text)
{
	//
	// Do not use writeCDATA here since this is obsolete according to the
	// QtAssistant documentation.
	//
	m_xmlStreamWriter.writeCDATA(text);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void XmlDomWriterStream::openElement(QString name)
{
	m_xmlStreamWriter.writeStartElement(name);
	m_openElements << name;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void XmlDomWriterStream::writeText(QString text)
{
	m_xmlStreamWriter.writeCharacters(text);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void XmlDomWriterStream::closeElement()
{
	if (! m_openElements.isEmpty())
		m_xmlStreamWriter.writeEndElement();

	m_attributes.clear();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

bool XmlDomWriterStream::hasAttribute(QString name) const
{
	return m_attributes.contains(name);
}
///////////////////////////////////////////////////////////////////////////////////////////////////




XmlDomReaderStream::XmlDomReaderStream(QXmlStreamReader& reader)
	: m_reader(reader)
	, m_currentElementConsumed(true) // step to first element (root element)
{}
///////////////////////////////////////////////////////////////////////////////////////////////////

XmlDomReaderStream::~XmlDomReaderStream()
{}
///////////////////////////////////////////////////////////////////////////////////////////////////




bool XmlDomReaderStream::nextElement(QString name /*= "" */)
{
	//
	// Workaround after a call to readText() where no text node has been
	// found. In that case, the current token is either a start element
	// or an end element. When there is no name specified in the call to
	// this function, we must be sure to first consume exactly this element
	// before any other
	//
	if(!m_currentElementConsumed)
	{
		if(name.isEmpty())
			return m_currentElementConsumed = m_reader.isStartElement();
		else
			return m_currentElementConsumed = (m_reader.isStartElement() && currentElement() == name);
	}

	//
	// Search for the next consumable element
	//
	while(!m_reader.atEnd())
	{
		switch(m_reader.readNext())
		{
		case QXmlStreamReader::StartElement: 
			m_attributeStack.push(m_reader.attributes());
			return (m_currentElementConsumed = (name.isEmpty() || name == currentElement()));
		case QXmlStreamReader::EndElement: 
			m_attributeStack.pop();
			return m_currentElementConsumed = false;
		case QXmlStreamReader::Invalid:
			checkError();
			break;
		case QXmlStreamReader::EndDocument:
			break;
		}
	}

	return m_currentElementConsumed = false;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void XmlDomReaderStream::checkError() const
{
	if(m_reader.hasError())
	{
		BOOSTEXT_THROW(DomParsingException(format("Error <%1%> detected while parsing xml document at line %2%, column %3%")
			% m_reader.errorString()
			% m_reader.lineNumber()
			% m_reader.columnNumber()));
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

bool XmlDomReaderStream::nextEndElement(QString name /*= "" */)
{
	//
	// Workaround after a call to readText() where no text node has been
	// found. In that case, the current token is either a start element
	// or an end element. When there is no name specified in the call to
	// this function, we must be sure to first consume exactly this element
	// before any other
	//
	if(!m_currentElementConsumed)
	{
		if(name.isEmpty())
			return m_currentElementConsumed = m_reader.isEndElement();
		else
			return m_currentElementConsumed = (m_reader.isEndElement() && currentElement() == name);
	}

	while(!m_reader.atEnd())
	{
		switch(m_reader.readNext())
		{
		case QXmlStreamReader::StartElement: 
			m_attributeStack.push(m_reader.attributes());
			return m_currentElementConsumed = false;
		case QXmlStreamReader::EndElement: 
			m_attributeStack.pop();
			return m_currentElementConsumed = (name.isEmpty() || name == currentElement());
		case QXmlStreamReader::Invalid:
			checkError();
			break;
		case QXmlStreamReader::EndDocument:
			break;
		}
	}

	return m_currentElementConsumed = false;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

QString XmlDomReaderStream::readText()
{
	if(m_reader.isCDATA() || m_reader.isCharacters())
		return m_reader.text().toString();

	while(! m_reader.atEnd())
	{
		switch(m_reader.readNext())
		{
		case QXmlStreamReader::Characters: 
			return m_reader.text().toString();
		case QXmlStreamReader::StartElement: 
			m_attributeStack.push(m_reader.attributes());
			m_currentElementConsumed = false;
			return "";
		case QXmlStreamReader::EndElement: 
			m_attributeStack.pop();
			m_currentElementConsumed = false;
			return "";
		case QXmlStreamReader::Invalid:
			checkError();
			return "";
		case QXmlStreamReader::EndDocument:
			break;
		}
	}

	return "";
}
///////////////////////////////////////////////////////////////////////////////////////////////////

QString XmlDomReaderStream::currentElement() const
{
	return m_reader.name().toString();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

bool XmlDomReaderStream::hasAttribute(QString attributeName, AttributeResolver::Type resolvingStrategy) const
{
	if(attributeName.isEmpty())
		BOOSTEXT_THROW(exception("Cannot query for an empty attribute"));

	if(resolvingStrategy == AttributeResolver::Local)
	{
		return m_attributeStack.size() && m_attributeStack.top().hasAttribute(attributeName);
	}

	QVectorIterator<QXmlStreamAttributes> it(m_attributeStack);
	it.toBack();
	while(it.hasPrevious())
		if(it.previous().hasAttribute(attributeName))
			return true;

	return false;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

QString XmlDomReaderStream::attribute(QString attributeName, AttributeResolver::Type resolvingStrategy) const
{
	if(!hasAttribute(attributeName, resolvingStrategy))
		BOOSTEXT_THROW(DomParsingException(format("Expected attribute <%1%> has not been found") % attributeName));

	if(resolvingStrategy == AttributeResolver::Local)
		return m_attributeStack.top().value(attributeName).toString();

	QVectorIterator<QXmlStreamAttributes> it(m_attributeStack);
	it.toBack();
	while(it.hasPrevious())
	{
		const QXmlStreamAttributes& attribs = it.previous();
		if (attribs.hasAttribute(attributeName))
			return attribs.value(attributeName).toString();
	}

	BOOSTEXT_THROW(DomParsingException(format("Expected attribute <%1%> has not been found") % attributeName));
}
///////////////////////////////////////////////////////////////////////////////////////////////////
